package lexico;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;

import lexico.Token.TipoToken;

public class LexicoEstados{
    
    //Atributos
    private String fuente;
    private Token token;
    private Estado estado;
    private Estado estadoInicial;
    private int pos;
    private int inicioLexema;
    private int linea; // se usa simplemente para identificar donde se encuentra el error
    private Hashtable<String, Estado> estados;
    private HashMap<String, String[]> tabla; 
    private String admitidos="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+-/%*<>=:;.')(@!{}";
 
    /**
     * Constructora
     */
    public LexicoEstados(){
    	estados = new Hashtable<String, Estado>();
        tabla = new HashMap<String, String[]>();
        addTabla("tipoVar","natural");
        addTabla("tipoVar","integer");
        addTabla("tipoVar","float");
        addTabla("tipoVar","boolean");
        addTabla("tipoVar","character");   
        addTabla("P","program");
        addTabla("D","vars");
        addTabla("D2","consts");
        addTabla("var","var");
        addTabla("const","const");
        addTabla("inst","instructions");
        addTabla("in","in");
        addTabla("out","out");
        addTabla("true","true");
        addTabla("false","false");
        addTabla("or","or");
        addTabla("and","and");
        addTabla("not","not");
        addTabla("S1","swap1");
        addTabla("S2","swap2");
        addTabla("vars","vars");
        addTabla("consts","consts");
    }
    
    // Metodos Privados
 
    /**
     * Devuelve un string con el tipo de palabra reservada
     */
    private String getPalReservada( String key){
    	
    	if (key == "P")   		 return "program";
    	else if( key == "D")     return "vars";
    	else if( key == "D2")    return "consts";
    	else if( key == "var")   return "var";
    	else if( key == "const") return "const";
    	else if( key == "inst")  return "instructions";
    	else if( key == "in")	 return "in";
        else if( key == "out")	 return "out";
        else if( key == "true")  return "true";
        else if( key == "false") return "false";
        else if( key == "or")    return "or";
        else if( key == "and")   return "and";
        else if( key == "not")   return "not";
        else if( key == "S1")    return "swap1";
        else if( key == "S2")    return "swap2";
        else if( key == "vars")  return "vars";
        else if( key == "consts")return "consts";
        else return "natural/integer/float/boolean/character";	
    }
    
    /**
     *
     * @param key
     * @param valor
     */
    private void addTabla(String key, String valor){
        String[] st;
        if(!tabla.containsKey(key)){
            st = new String[10];
            st[0] = valor;
            tabla.put(key, st);
        }
        else{
            int pos = 0;
            boolean rep = false;
            st = tabla.get(key);
            while(pos<10 && !rep && st[pos] != null){
                rep = st[pos].equals(valor);
                pos++;
            }
            if(!rep){
                st[pos] = valor;
                tabla.put(key, st);
            }
        }
}
    /**
     * Esta funcion retorna el siguiente token, por medio del automata caracter a caracter hasta que llega
     * a un estado de aceptacion y crea un nuevo tipo token con el lexico que devuelve el estado final y
     * el tipo toquen que pertenece.
     * @return
     * @throws Exception
     */
    private Token getNextToken( ArrayList<String> ListError,boolean error) throws Exception{
    	Token token = null;
    	while (token == null){
    		char actual = 0;
    		if ( pos==fuente.length() ){
    			if(estado instanceof EstadoAceptacion){
    				String lex = fuente.substring(inicioLexema, Math.min(pos, fuente.length()));
    				token  = estado.getToken(lex);
    				estado = estados.get(estado.getSigEstado());
    				return token;
    			}
    			//devolvemos el token de final de linea
    			return new Token(TipoToken.tk_EOF,null);
    		}else if(pos < fuente.length()) actual = fuente.charAt(pos);
    		
    		if(actual=='@' && estado.estadoInicial()){
    			//si empieza por @ es un comentario
    			while((actual != '\n') && (pos < fuente.length()-1)){
                    //lo implementamos de esta forma ya que los comentarios pueden no empezar en el inicio de linea
    				pos++;
                    inicioLexema++;
                    actual = fuente.charAt(pos);
            }
    			if(pos == fuente.length()-1)
    				//devolvemos el token de final de linea
                    return new Token(TipoToken.tk_EOF, null);
    		}
    		if(estado.estadoInicial() && (actual == '\n'))
                linea++;
  
    		String lex;
    		boolean admitido=false;
    		for(int i=0;i<admitidos.length();i++){
    			if(admitidos.charAt(i)==actual){
    				admitido=true;
    				break;
    			}
    		}
    		
    		if (actual==' ' || actual=='\n'){
    			admitido=true;
    		}
    		if (!admitido){
    			ListError.add("LINE: " + linea + " - ERROR LEXICO:Token  No existe ningun token del tipo <" + fuente.substring(inicioLexema, Math.min(pos+1, fuente.length())) + ">");
    	    	ListError.add("LINE: " + linea + " Violacion de lenguaje"); 
    	    	error = true;
    			throw new Exception("LINE: " + linea + " Violacion de lenguaje");
    		}
    		
    		if (inicioLexema > Math.min(pos, fuente.length()-1 ))
    			return null;
    		else
    			lex = fuente.substring( inicioLexema, Math.min(pos, fuente.length()-1) );
    		
    		//solo los estados de aceptacion devuelven tokens
    		token = estado.getToken(lex);
            int movimiento;
			movimiento = estado.transita(actual);
			
			/* 1 = hacia delante, 0=repite estado, -1= retrocede
			 * si el estado actual es de no aceptacion y el movimiento es quedarse, 
			 * no se avanza luego no es un token o palabra reservada
            */
            if((estado instanceof EstadoNoAceptacion) && (movimiento == 0)){
            	ListError.add("LINE: " + linea + " - ERROR LEXICO:Token  No existe ningun token del tipo <" + fuente.substring(inicioLexema, Math.min(pos+1, fuente.length())) + ">");
            	error=true;	
            }
            
            pos += movimiento;
            estado = estados.get(estado.getSigEstado());
            if (estado.estadoInicial())
                    inicioLexema++;
    	}// end while
    	
    	inicioLexema = pos;
    	return token;          
    }
    /**
     * 
     * @param tipo
     * @return
     */
    private boolean esReservada(String tipo){
    	if (tipo == null || !tabla.containsKey(tipo) ){
    		return false;
    	}else{
    		int i=0;
    		boolean esta=false;
    		String valores[] = tabla.get(tipo);
    		while (!esta && i<valores.length){
    			if (valores[i]!=null){
    				String p = token.getLexema();
    				esta= valores[i].compareTo(p) == 0 ;
    			}
    			i++;
    		}
    		return esta;
    	}
    }
    
    //metodos publicos
    
    /**
     * 
     * @param fuente
     * @throws Exception
     */
    public void inicializa(String fuente, ArrayList<String> listError,boolean error) throws Exception {
    	this.fuente=fuente;
    	this.estado=this.estadoInicial;
    	this.pos=0;
    	this.inicioLexema=0;
    	this.linea=1;
    	this.token=this.getNextToken(listError,error);
    }
    /**
     * devuelve la linea del codigo a compilar por la que va 
     * (se usa para identificar errores)
     * @return
     */
    public int getLinea(){
    	return this.linea;
    }
    
    public void addEstado(Estado estado){
    	this.estados.put(estado.getID(), estado);
    	if(estado.estadoInicial()){
    		this.estadoInicial=estado;
    	}
    }
    /**
     * 
     * @param tipo
     * @param palabra
     * @return si la palabra es del tipo esperado o si el token es del tipo 
     * esperado
     * @throws Exception
     */
    public boolean tipoCorrecto(TipoToken tipo, String palabra){
    	if(palabra!=null && tipo==TipoToken.tk_iden){
    		if(this.esReservada(palabra)){
    			return true;
    		}else return false;
    	}else{
    		return token.getTipo().equals(tipo);
    	}
    }
    
    /**
     * decide si la palabra que entra pertenece o no al lenguaje. 
     * A traves de los tokens decide si sigue las reglas sintacticas
     * @param tipo
     * @param palRes
     * @return token reconocido
     * @throws Exception
     */
    public Token reconoce(TipoToken tipo, String palRes, ArrayList<String> ListError, boolean error) throws Exception {
    	
        if(!tipoCorrecto(tipo, null)){
    		ListError.add("LINE: " + linea + " - ERROR LEXICO:Token " + token.getLexema() + " no reconocido como tipo " + tipo);
    		error=true;
        }
        if(tipo.compareTo(TipoToken.tk_iden) == 0 && palRes != null)
            if (!esReservada(palRes)){
	        	ListError.add("LINE: " + linea + " - ERROR LEXICO: Token " + token.getLexema() + " se esperaba una palabra reservada de tipo: " + getPalReservada(palRes) );
	            error=true;
            }
        Token res = token;
        token = getNextToken(ListError,error);
        return res;
    }
    /**
     * True si el token el nulo, false en caso contrario.
     * @return 
     */
    public boolean fallo(){
    	return token == null;
    }
}